package com.xw.unification.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xw.unification.condition.UnifiedResponseCondition;
import com.xw.unification.entity.ResultCodeEnumDefaultResolver;
import com.xw.unification.entity.ResultCodeEnumEmptyResolver;
import com.xw.unification.handler.UnifiedResponseHandler;
import com.xw.unification.helper.StringHelper;
import com.xw.unification.interfaces.ResultCodeEnumResolvable;
import com.xw.unification.interfaces.ResultCodeEnumerable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 统一响应体组件自动配置类
 *
 * @author qiwei
 * @date 2023-8-9 21:00:00
 * @since v1.0.0
 */
@Configuration
@AutoConfigureAfter(WebUnificationMvcBaseConfig.class)
@EnableConfigurationProperties({WebUnificationMvcProperties.class})
@Conditional(UnifiedResponseCondition.class)
public class WebUnificationMvcUnifiedResponseConfig implements WebMvcConfigurer {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebUnificationMvcUnifiedResponseConfig.class);

    private WebUnificationMvcProperties.UnifiedResponseProperties responseProperties;

    @Autowired
    public WebUnificationMvcUnifiedResponseConfig(WebUnificationMvcProperties webUnificationMvcProperties) {
        LOGGER.info("[WebUnificationMvcUnifiedResponseConfig] ### UnifiedResponseConfig auto configuration");
        this.responseProperties = webUnificationMvcProperties.getUnifiedResponse();
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        List<HttpMessageConverter<?>> converterList = new ArrayList<>();
        if (responseProperties.isAggressiveConvertContentTypeToJson()) {
            converters.add(0, new CustomizedMappingJackson2HttpMessageConverter());
        } else {
            Iterator<HttpMessageConverter<?>> iterator = converters.iterator();
            HttpMessageConverter<?> converter;
            while (iterator.hasNext()) {
                converter = iterator.next();
                if (converter instanceof MappingJackson2HttpMessageConverter) {
                    iterator.remove();
                    converterList.add(converter);
                }
            }
            if (converterList.isEmpty()) {
                converters.add(0, new MappingJackson2HttpMessageConverter());
            } else {
                for (int i = 0; i < converterList.size(); i++) {
                    converters.add(i, converterList.get(i));
                }
            }
        }
    }

    public static class CustomizedMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

        public CustomizedMappingJackson2HttpMessageConverter() {
            this(Jackson2ObjectMapperBuilder.json().build());
        }

        public CustomizedMappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
            super(objectMapper);
            setSupportedMediaTypes(Collections.singletonList(new MediaType("*", "*")));
        }
    }

    @SuppressWarnings("all")
    @Bean("resultCodeEnumResolver")
    @ConditionalOnMissingBean(ResultCodeEnumResolvable.class)
    public ResultCodeEnumResolvable<? extends Enum<?>> resultCodeEnumResolver(
            WebUnificationMvcProperties webUnificationMvcProperties) {
        WebUnificationMvcProperties.UnifiedResponseProperties properties = webUnificationMvcProperties.getUnifiedResponse();
        WebUnificationMvcProperties.UnifiedResponseResult result = properties.getResult();
        Class<?> rawType = result.getEnumClass();
        if (rawType == null) {
            LOGGER.warn("[WebUnificationMvcUnifiedResponseConfig] ### No customized error-code-enum-class");
            LOGGER.warn("[WebUnificationMvcUnifiedResponseConfig] ### ResultCodeEnumEmptyResolver has been " +
                    "initialized So That 'web.unification-config.unified-response.error-status-message-mapping' " +
                    "Is No Use");
            return new ResultCodeEnumEmptyResolver<>();
        }
        if (!Enum.class.isAssignableFrom(rawType)
                || !ResultCodeEnumerable.class.isAssignableFrom(rawType)) {
            throw new ClassCastException("Class must extend Enum and ResultCodeEnumerable");
        }
        String successEnumName = result.getDefaultSuccessEnum();
        String errorEnumName = result.getDefaultErrorEnum();
        if (StringHelper.hasText(successEnumName) || StringHelper.hasText(errorEnumName)) {
            try {
                Enum<?> successEnum = successEnumName == null ? null : Enum.valueOf((Class) rawType, successEnumName);
                Enum<?> errorEnum = errorEnumName == null ? null : Enum.valueOf((Class) rawType, errorEnumName);
                ResultCodeEnumerable.DEFAULT.setDefaultSuccessAndErrorEnum(
                        (ResultCodeEnumerable) successEnum, (ResultCodeEnumerable) errorEnum);
            } catch (IllegalStateException e) {
                LOGGER.warn(e.getMessage());
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
                throw e;
            }
        }
        LOGGER.info("[WebUnificationMvcUnifiedResponseConfig] ### ResultCodeEnumDefaultResolver has been initialized");
        return new ResultCodeEnumDefaultResolver<>((Class)rawType);
    }

    @RestControllerAdvice
    @Conditional(UnifiedResponseCondition.class)
    static class InnerUnifiedResponseHandler extends UnifiedResponseHandler {

        private final List<String> basePackages;

        @Autowired
        public InnerUnifiedResponseHandler(ResultCodeEnumResolvable<?> resultCodeEnumResolver,
                                           WebUnificationMvcProperties webUnificationMvcProperties) {
            super(resultCodeEnumResolver, webUnificationMvcProperties);
            basePackages = Optional.ofNullable(webUnificationMvcProperties.getUnifiedResponse().getBasePackages())
                    .orElse(new ArrayList<>()).stream()
                    .filter(StringHelper::hasText)
                    .map(String::trim)
                    .collect(Collectors.toList());
        }

        protected List<String> getBasePackages() {
            return basePackages;
        }

    }


}
